package pos

import (
	"context"
	coolenvpb "coolcar/coolenv/api/gen/v1"
	"math"
	"math/rand"
	"time"

	cmap "github.com/orcaman/concurrent-map"
	"go.uber.org/zap"
)

const (
	ARC      = 6371000.0 //6378137.0
	rad      = math.Pi / 180.0
	minSpeed = 6
	maxSpeed = 25
)

// Publisher defines the publish interface.
type Publisher interface {
	Publish(context.Context, *coolenvpb.CarPosUpdate) error
}

// controller defines a car position simulation controller.
type controller struct {
	carPosChans cmap.ConcurrentMap
	Publisher
	PosSimInterval time.Duration
	Logger         *zap.Logger
}

// Controller instantiation controller
func Controller(carPosChans cmap.ConcurrentMap, pub Publisher, posSimInterval time.Duration, log *zap.Logger) *controller {
	return &controller{
		carPosChans:    carPosChans,
		Publisher:      pub,
		PosSimInterval: posSimInterval,
		Logger:         log,
	}
}

// RunPosSimulations runs position simulations for car.
func (c *controller) RunPosSimulations(ctx context.Context, carPos *coolenvpb.CarPosUpdate) {
	posFanoutCh := make(chan struct{})
	c.carPosChans.Set(carPos.CarId, &posFanoutCh) // 把需要模拟位置的carID，以及channel添加到ConcurrentMap中
	go c.SimulateCarPos(context.Background(), carPos, posFanoutCh)
}

// SimulateCarPos simulates a single car position.
func (c *controller) SimulateCarPos(ctx context.Context, initial *coolenvpb.CarPosUpdate, carPosCh chan struct{}) {
	var position *coolenvpb.Location = initial.Pos
	for {
		select {
		case <-time.After(c.PosSimInterval):
		case <-carPosCh:
			return
		}
		// 生成模拟数据
		position = c.updatePosition(ctx, position)
		// 把模拟数据送到对应CarID的channel中
		c.Publisher.Publish(ctx, &coolenvpb.CarPosUpdate{
			CarId: initial.CarId,
			Pos:   position,
		})
	}
}

func (c *controller) updatePosition(ctx context.Context, initial *coolenvpb.Location) *coolenvpb.Location {
	lat1 := initial.Latitude   // 起始点纬度
	long1 := initial.Longitude // 起始点经度

	distanceM := float64(minSpeed+rand.Intn(maxSpeed-minSpeed)) / 3.6 // 随机移动距离
	radian := rad * float64(rand.Intn(360))                           // 方位角转弧度

	lat2 := lat1 + (distanceM * math.Cos(radian) / (ARC * rad))                    // 终点纬度
	long2 := long1 + (distanceM * math.Sin(radian) / (ARC * math.Cos(lat1) * rad)) // 终点经度

	return &coolenvpb.Location{
		Latitude:  lat2,
		Longitude: long2,
	}
}
